﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using Algobox.JuicyLib.IO.CSV;
using System.IO;
using Algobox.JuicyLib.SQL.LiquidView;
using Algobox.JuicyLib.Net;

namespace Algobox.JuicyLib.Apps.Sterling
{
    public class SterlingTradeImporter : IDisposable
    {
        /// <summary>
        /// Import trades from Sterling trade file dumps
        /// </summary>
        /// <param name="processname">Name of the executing process</param>
        /// <param name="hostname">Host name that identifies the trades in the database</param>
        /// <param name="dbConnection">Connection string to access the LiquidViewLinqDataContext</param>
        public SterlingTradeImporter(string processname, string archiveDir, string dbConnection)
        {
            _linq = new Algobox.JuicyLib.SQL.LiquidView.LiquidViewLinqDataContext(dbConnection);
            PROCESSNAME = processname;
            ArchiveDirectory = archiveDir;
        }

        readonly string PROCESSNAME = "SterlingTradeImporter";
        readonly string HOSTNAME = "Sterling-" + DateTime.Now.Year;

        public string ArchiveDirectory { get; set; }

        Algobox.JuicyLib.SQL.LiquidView.LiquidViewLinqDataContext _linq;

        public void Dispose()
        {
            _linq.Dispose();
        }


        public void RunDailyTrades(string destinationUri, string username, string password, List<string> ignoreAccounts)
        {
            List<string> files;
            JuicyFTP ftp = new JuicyFTP(username, password, destinationUri, JuicyFTP.FTPMode.Windows, JuicyFTP.FTPCommand.Passive);
            
            if(!ftp.TryGetFileList(out files))
            {
                Console.WriteLine("Failed to load file list from " + destinationUri);
                _linq.ExceptionHandlerThrow(PROCESSNAME, "Failed to load file list from " + destinationUri, null);
                return;
            }

            using (WebClient client = new WebClient())
            {
                client.Credentials = new NetworkCredential(username, password);
                List<string> importedFiles = _linq.LogsFileImports.Where(a => a.LogSource == PROCESSNAME).Select(a => a.Filename).ToList();
                foreach (string filename in files)
                {
                    try
                    {
                        if (importedFiles.Contains(filename))
                            continue;
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine("Failed to check LogsFileImports for existing file - " + e.Message);
                        _linq.ExceptionHandlerThrow(PROCESSNAME, "Failed to check LogsFileImports for existing file - " + e.Message, null);
                        continue;
                    }

                    Console.WriteLine("Importing new trade file " + filename);
                    
                    try
                    {
                        byte[] data = client.DownloadData(destinationUri + filename);
                        string file = System.Text.Encoding.ASCII.GetString(data);

                        ImportDailyTrades(filename, file, ignoreAccounts);

                        LogsFileImport row = new LogsFileImport()
                        {
                            LogSource = PROCESSNAME,
                            Filename = filename,
                            TimeStamp = DateTime.Now
                        };
                        _linq.LogsFileImports.InsertOnSubmit(row);

                    }
                    catch (Exception e)
                    {
                        Console.WriteLine("Failed to download " + filename + " from " + destinationUri + "\r\n" + e);
                        _linq.ExceptionHandlerThrow(PROCESSNAME, "Failed to download "+filename+" from " + destinationUri + " - " + e, null);
                    }
                }
            }
            _linq.SubmitChanges();
        }


        private void ImportDailyTrades(string source, string data, List<string> ignoreAccounts)
        {
            int countMissing = 0;
            int countSuccess = 0;

            try
            {
                const char SEPERATOR_COL = ',';
                const int IX_ACCOUNT = 2;
                const int IX_SIDE = 3;
                const int IX_SYMBOL = 4;
                const int IX_QUANTITY = 5;
                const int IX_PRICE = 6;
                const int IX_DEST = 7;
                const int IX_TIME = 9;
                const int IX_CUSIP = 11;
                const int IX_FLAG = 12;
                const int IX_ORDERID = 13;
                const int IX_RECORDID = 39;
                const int IX_CPTY = 45;

                string archiveDir;
                DateTime fileDate;

                archiveDir = Path.GetFileNameWithoutExtension(source);
                archiveDir = archiveDir.Substring(archiveDir.Length - 8);
                fileDate = DateTime.ParseExact(archiveDir, "yyyyMMdd", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None);
                archiveDir = ArchiveDirectory + fileDate.ToString("yyyy/MM/dd") + "/";

                List<string> tradeKeys = _linq.Trades.Where(x => x.Host == HOSTNAME && x.TradeTime > fileDate && x.TradeTime < fileDate.AddDays(1)).Select(t => t.ExchangeOrderId + t.ExchangeTradeId).ToList();

                using (CsvReader csv = new CsvReader(new StringReader(data), true, SEPERATOR_COL, '"', '\0', '#', true))
                {
                    string exchange, orderId, tradeId, account, routedMIC;
                    DateTime tradeTime;
                    long quantity;
                    decimal price;
                    int index;
                    bool isAggressor;

                    csv.DefaultParseErrorAction = ParseErrorAction.AdvanceToNextLine;
                    while (csv.ReadNextRecord())
                    {
                        try
                        {
                            account = csv[IX_ACCOUNT];
                            orderId = csv[IX_ORDERID];
                            index = orderId.IndexOf('-');
                            tradeId = csv[IX_RECORDID] + orderId.Substring(index, orderId.Length - index);
                            orderId = "E-" + account + "-" + csv[IX_ORDERID];

                            // if trade exists then continue
                            if (tradeKeys.Contains(orderId + tradeId))
                                continue;

                            if (ignoreAccounts.Contains(account))
                                continue;

                            ++countMissing;

                            // quantity
                            if (!Int64.TryParse(csv[IX_QUANTITY], out quantity))
                            {
                                JuicyLib.SQL.LiquidView.LiquidViewHelper.LogException(PROCESSNAME, "Failed to parse quantity for record Account({0}) Symbol({1}) Quantity({2}) Price({3}) Date({4}) OrderId({5}) TradeId({6})", account, csv[IX_SYMBOL], csv[IX_QUANTITY], csv[IX_PRICE], csv[IX_TIME], orderId, tradeId);
                                Console.WriteLine("Can't parse quantity:  " + csv[IX_QUANTITY]);
                                continue;
                            }

                            // quantity side
                            if (csv[IX_SIDE] != "B")
                                quantity = -quantity;

                            // price
                            if (!Decimal.TryParse(csv[IX_PRICE], out price))
                            {
                                JuicyLib.SQL.LiquidView.LiquidViewHelper.LogException(PROCESSNAME, "Failed to parse price for record Account({0}) Symbol({1}) Quantity({2}) Price({3}) Date({4}) OrderId({5}) TradeId({6})", account, csv[IX_SYMBOL], csv[IX_QUANTITY], csv[IX_PRICE], csv[IX_TIME], orderId, tradeId);
                                Console.WriteLine("Can't parse price:  " + csv[IX_PRICE]);
                                continue;
                            }

                            // trade time
                            if (!DateTime.TryParseExact(csv[IX_TIME],
                                   "MM/dd/yyyy HH:mm:ss",
                                   System.Globalization.CultureInfo.InvariantCulture,
                                   System.Globalization.DateTimeStyles.None,
                                   out tradeTime))
                            {
                                JuicyLib.SQL.LiquidView.LiquidViewHelper.LogException(PROCESSNAME, "Failed to parse date for record Account({0}) Symbol({1}) Quantity({2}) Price({3}) Date({4}) OrderId({5}) TradeId({6})", account, csv[IX_SYMBOL], csv[IX_QUANTITY], csv[IX_PRICE], csv[IX_TIME], orderId, tradeId);
                                Console.WriteLine("Can't parse date:  " + csv[IX_TIME]);
                                continue;
                            }

                            // routed MIC
                            if (csv[IX_FLAG].Contains('X') || csv[IX_FLAG].Contains('7'))
                                routedMIC = "XXXX";
                            else
                                routedMIC = null;

                            // Aggressor
                            isAggressor = csv[IX_FLAG].Contains('R') || csv[IX_FLAG].Contains('1') || csv[IX_FLAG].Contains('6');

                            // ExchangeMIC
                            exchange = GetExchangeMIC(csv[IX_DEST]);

                            // insert trade into the database
                            int result = _linq.ImportTradeBySterlingSymbol(
                                HOSTNAME,
                                orderId,
                                tradeId,
                                exchange,
                                orderId,
                                tradeId,
                                account,
                                null,
                                csv[IX_SYMBOL],
                                quantity,
                                price,
                                tradeTime,
                                csv[IX_CPTY],
                                isAggressor,
                                false,
                                false,
                                null,
                                PROCESSNAME + " imported from " + source,
                                false,
                                null,
                                routedMIC,
                                csv[IX_FLAG],
                                null);

                            if (result == 0)
                            {
                                ++countSuccess;
                            }
                            else
                            {
                                Console.WriteLine("Failed ImportTradeBySterlingSymbol Returned({7}) Account({0}) Symbol({1}) Quantity({2}) Price({3}) Date({4}) OrderId({5}) TradeId({6})", csv[IX_ACCOUNT], csv[IX_SYMBOL], csv[IX_QUANTITY], csv[IX_PRICE], csv[IX_TIME], csv[IX_ORDERID], csv[IX_RECORDID], result);
                                JuicyLib.SQL.LiquidView.LiquidViewHelper.LogException(PROCESSNAME, "Failed ImportTradeBySterlingSymbol Returned({7}) Account({0}) Symbol({1}) Quantity({2}) Price({3}) Date({4}) OrderId({5}) TradeId({6})", csv[IX_ACCOUNT], csv[IX_SYMBOL], csv[IX_QUANTITY], csv[IX_PRICE], csv[IX_TIME], csv[IX_ORDERID], csv[IX_RECORDID], result);
                            }
                        }
                        catch (System.Resources.MissingManifestResourceException e)
                        {

                        }
                        catch (Exception e)
                        {
                            Console.WriteLine("Failed to import trade!\r\n" + e);
                            JuicyLib.SQL.LiquidView.LiquidViewHelper.LogException(PROCESSNAME, "Failed to import trade Account({0}) Symbol({1}) Quantity({2}) Price({3}) Date({4}) OrderId({5}) TradeId({6}) Exception({7})", csv[IX_ACCOUNT], csv[IX_SYMBOL], csv[IX_QUANTITY], csv[IX_PRICE], csv[IX_TIME], csv[IX_ORDERID], csv[IX_RECORDID], e.Message);
                            continue;
                        }
                    }
                }
                                
                if (!CheckDirectory(archiveDir))
                {
                    Console.WriteLine("Could not create directory " + archiveDir + ", " + source + " not archived");
                    JuicyLib.SQL.LiquidView.LiquidViewHelper.LogException(PROCESSNAME, "Could not create directory " + archiveDir + ", " + source + " not archived");
                }
                else
                {
                    File.WriteAllText(archiveDir + source, data, Encoding.ASCII);
                    Console.WriteLine("Successfully archived " + source);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Failed to import trades!\r\n" + e);
                _linq.ExceptionHandlerThrow(PROCESSNAME, "Failed to import trades - " + e.Message, null);
            }

            if (countMissing > 0)
            {
                Console.WriteLine("Imported {0} successfully of {1} missing trades", countSuccess, countMissing);
                _linq.ExceptionHandlerThrow(PROCESSNAME, String.Format("Imported {0} successfully of {1} missing trades", countSuccess, countMissing), null);
            }
        }

        private string GetExchangeMIC(string destination)
        {
            switch (destination)
            {
                case "LSE":
                    return "XLON";
                case "BATSCYCLE":
                    return "BATE";
                case "BATSEU":
                    return "BATE";
                case "BATSPP":
                    return "BATE";
                case "BATSRC":
                    return "BATE";
                case "BATSRL":
                    return "BATE";
                case "CHIXEU":
                    return "CHIX";
                case "ARCA":
                    return "ARCX";
                case "BATS":
                    return "BATS";
                case "NSDQ":
                    return "XNAS";
                case "NYSE":
                    return "XNYS";
            }
            return destination;
        }


        private bool CheckDirectory(string directory)
        {
            if (directory == String.Empty)
                return false;

            if(directory[0] == '/')
                directory = directory.Substring(1);
            
            try
            {
                if (Directory.Exists(directory))
                    return true;

                Directory.CreateDirectory(directory);
                return true;
            }
            catch (Exception e)
            {
                LiquidViewHelper.LogException(PROCESSNAME, "Create directory failed for "+ directory +" with Exception : " + e.Message);
                return false;
            }
        }
    }
}
